#ifndef SWEGL_MATRIX4_H
#define SWEGL_MATRIX4_H

namespace swegl
{

// all matrices are column major matrices and stored as column major
// [Rotation] [Translation]
// [Projection            ]
// vector transformation: M * v
// [ m[0][0] m[1][0] m[2][0] m[3][0] ]   [x]
// [ m[0][1] m[1][1] m[2][1] m[3][1] ] * [y]
// [ m[0][2] m[1][2] m[2][2] m[3][2] ]   [z]
// [ m[0][3] m[1][3] m[2][3] m[3][3] ]   [w]

class Matrix4x4
{
public:
	Matrix4x4();
	Matrix4x4(const Vec4 &x, const Vec4 &y, const Vec4 &z, const Vec4 &w);
	Matrix4x4(const Matrix3x3 &rotation, const Vec3 &translation);
	explicit Matrix4x4(const float *ptr);
	// first row, then second row, ...
	Matrix4x4(float xx, float xy, float xz, float xw,
	          float yx, float yy, float yz, float yw,
	          float zx, float zy, float zz, float zw,
	          float wx, float wy, float wz, float ww);

	const Vec4 column(uint index) const;
	const Vec4 &operator[](uint index) const;
	Vec4 &operator[](uint index);

	Matrix4x4 operator * (const Matrix4x4 &rhs) const;
	Matrix4x4 operator + (const Matrix4x4 &rhs) const;
	Matrix4x4 operator - (const Matrix4x4 &rhs) const;

	Matrix4x4 &operator *= (const Matrix4x4 &rhs);
	Matrix4x4 &operator += (const Matrix4x4 &rhs);
	Matrix4x4 &operator -= (const Matrix4x4 &rhs);

	bool operator == (const Matrix4x4 &rhs) const;
	bool operator != (const Matrix4x4 &rhs) const;
	bool isEqual(const Matrix4x4 &rhs) const;
	bool isEqual(const Matrix4x4 &rhs, float epsilon) const;

	// transforms the given 3d vector by the matrix. w component is considered to be 1.0
	Vec3 operator * (const Vec3 &v) const;
	// transforms the given 3d vector by the matrix and project the result into w = 1 (ignores projection)
	//Vec3 transformAffine(const Vec3 &v) const;
	// transforms the given 4d vector by the matrix
	Vec4 transform(const Vec4 &v) const;
	// transforms the given 4d vector by the matrix but ignores projection. result.w = v.w
	//Vec4 transformAffine(const Vec4 &v) const;
	// only apply rotation to the given 3d vector
	//Vec3 rotate(const Vec3 &v) const;

	void lookAt(const Vec3 &from, const Vec3 &at, const Vec3 &up);
	void objectLookAt(const Vec3 &from, const Vec3 &at, const Vec3 &up);
	void setPerspective(float fovY, float aspectRatio, float zNear, float zFar);
	void setOrtho(float left, float right, float bottom, float top, float zNear, float zFar);

	// set the rotation part of the matrix. Keep translation and projection untouched
	void setRotation(const Matrix3x3 &rotation);
	// set the translation part of the matrix. Keep rotation and projection untouched
	void setTranslation(const Vec3 &translation);
	// return the rotation part of the matrix
	Matrix3x3 rotation() const;
	// return the translation part of the matrix
	Vec3 translation() const;
	
	void affineInvert();
	void invert();
	static Matrix4x4 Invert(const Matrix4x4 &m);

	// cast operator
	const float *toFloatPtr() const;
	float *toFloatPtr();

	static const Matrix4x4 Zero;
	static const Matrix4x4 Identity;

private:
	Vec4 m[4];
};

inline Matrix4x4::Matrix4x4()
{
}

inline Matrix4x4::Matrix4x4(const Vec4 &x, const Vec4 &y, const Vec4 &z, const Vec4 &w)
{
	m[0] = x;
	m[1] = y;
	m[2] = z;
	m[3] = w;
}

inline Matrix4x4::Matrix4x4(const Matrix3x3 &rotation, const Vec3 &translation)
{
	const Vec3 &i = rotation[0];
	m[0].set(i.x, i.y, i.z, 0);
	const Vec3 &j = rotation[1];
	m[1].set(j.x, j.y, j.z, 0);
	const Vec3 &k = rotation[2];
	m[2].set(k.x, k.y, k.z, 0);
	m[3].set(translation.x, translation.y, translation.z, 1);
}

inline Matrix4x4::Matrix4x4(const float *ptr)
{
	m[0].x = ptr[0];
	m[0].y = ptr[1];
	m[0].z = ptr[2];
	m[0].w = ptr[3];
	m[1].x = ptr[4];
	m[1].y = ptr[5];
	m[1].z = ptr[6];
	m[1].w = ptr[7];
	m[2].x = ptr[8];
	m[2].y = ptr[9];
	m[2].z = ptr[10];
	m[2].w = ptr[11];
	m[3].x = ptr[12];
	m[3].y = ptr[13];
	m[3].z = ptr[14];
	m[3].w = ptr[15];
}

inline Matrix4x4::Matrix4x4(float xx, float xy, float xz, float xw,
                            float yx, float yy, float yz, float yw,
                            float zx, float zy, float zz, float zw,
                            float wx, float wy, float wz, float ww)
{
	m[0].x = xx;
	m[0].y = xy;
	m[0].z = xz;
	m[0].w = xw;
	m[1].x = yx;
	m[1].y = yy;
	m[1].z = yz;
	m[1].w = yw;
	m[2].x = zx;
	m[2].y = zy;
	m[2].z = zz;
	m[2].w = zw;
	m[3].x = wx;
	m[3].y = wy;
	m[3].z = wz;
	m[3].w = ww;
}

inline const Vec4 Matrix4x4::column(uint index) const
{
	return Vec4(m[0][index], m[1][index], m[2][index], m[3][index]);
}

inline const Vec4 &Matrix4x4::operator[](uint index) const
{
	return m[index];
}

inline Vec4 &Matrix4x4::operator[](uint index)
{
	return m[index];
}

inline bool Matrix4x4::operator == (const Matrix4x4 &rhs) const
{
	return isEqual(rhs);
}

inline bool Matrix4x4::operator != (const Matrix4x4 &rhs) const
{
	return ! isEqual(rhs);
}

inline bool Matrix4x4::isEqual(const Matrix4x4 &rhs) const
{
	return m[0] == rhs.m[0] && m[1] == rhs.m[1] && m[2] == rhs.m[2];
}

inline bool Matrix4x4::isEqual(const Matrix4x4 &rhs, float epsilon) const
{
	return m[0].isEqual(rhs.m[0], epsilon)
	       && m[1].isEqual(rhs.m[1], epsilon)
	       && m[2].isEqual(rhs.m[2], epsilon)
	       && m[3].isEqual(rhs.m[3], epsilon);
}

inline const float *Matrix4x4::toFloatPtr() const
{
	return m[0].toPtr();
}

inline float *Matrix4x4::toFloatPtr()
{
	return m[0].toPtr();
}

}

#endif
